package com.lx.spring.web;

import com.lx.spring.mapper.IOrderMapper;
import com.lx.spring.mapper.OrderMapper;
import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;


public class AopApplicationContext extends MyAnnotationConfigApplicationContext {

    //切面对象
    List<Object> aspectBeans = new ArrayList<>();
    // 切点信息
    Set<AspectInfo> aspects = new HashSet<>();

    //需要代理的对象
    Map<String, Object> proxyBeanMap = new HashMap<>();

    public AopApplicationContext(Class configClass) throws Exception {
        super(configClass);
        //自定义AOP
        // 1.筛选切面对象 @Aspect
        for (Object bean : singtonObjects.values()) {
            if (bean.getClass().getAnnotation(Aspect.class) != null) {
                aspectBeans.add(bean);
            }
        }

        // 2. 分析 切面对象 提取切点
        for (Object aspectBean : aspectBeans) {
            Method[] methods = aspectBean.getClass().getDeclaredMethods();
            for (Method method : methods) {
                //只关注 增强方法 @Before, @After ...
                if(method.getAnnotation(Before.class) != null){
                    //前置增强方法
                    AspectInfo aspectInfo = new AspectInfo();
                    aspectInfo.adviceMethod = method;
                    aspectInfo.aspectBean = aspectBean;
                    Before before = method.getAnnotation(Before.class);
                    aspectInfo.pointcut = before.value();
                    aspectInfo.replacePointcut();
                    aspects.add(aspectInfo);
                }else if(method.getAnnotation(After.class) != null){
                    //后置增强方法
                    AspectInfo aspectInfo = new AspectInfo();
                    aspectInfo.adviceMethod = method;
                    aspectInfo.aspectBean = aspectBean;
                    After after = method.getAnnotation(After.class);
                    aspectInfo.pointcut = after.value();
                    aspectInfo.replacePointcut();
                    aspects.add(aspectInfo);
                }else if(method.getAnnotation(AfterReturning.class) != null){
                    //后置增强方法
                    AspectInfo aspectInfo = new AspectInfo();
                    aspectInfo.adviceMethod = method;
                    aspectInfo.aspectBean = aspectBean;
                    AfterReturning afterReturning = method.getAnnotation(AfterReturning.class);
                    aspectInfo.pointcut = afterReturning.value();
                    aspectInfo.replacePointcut();
                    aspects.add(aspectInfo);
                }
            }
        }
        System.out.println("--------------切面对象------------------");
        aspectBeans.forEach(System.out::println);
        System.out.println("--------------切面信息------------------");
        aspects.forEach(System.out::println);
        // 3. 通过切点判断 IOC 容器中的对象, 那些对象是切到的对象
        for (Map.Entry<String, Object> entry : singtonObjects.entrySet()) {
            Object bean = entry.getValue();
            String id = entry.getKey();
            a:for (AspectInfo aspect : aspects) {
                String pointcut = aspect.pointcut;
                String pointcutRegex = replaceTOClassPath(pointcut);
                //对业务对象的方法进行遍历
                for (Method method : bean.getClass().getDeclaredMethods()) {
                    String methodStr = method.toString();
                    if(methodStr.matches(pointcutRegex)){
                        //被切到的方法
                        System.out.printf(" 被切到的 %s => %s \n", id, bean);
                        //把被切到方法的对象加入代理对象集合
                        proxyBeanMap.put(id, bean);
                        break a;
                    }
                }

            }

        }
        // 4. 包含被切到的方法的对象 => 增强(JDK, CGLIB)
        for (Map.Entry<String, Object> entry : proxyBeanMap.entrySet()) {
            String id = entry.getKey();
            Object bean = entry.getValue();
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            if(interfaces == null || interfaces.length == 0){
                //未实现任何接口 => CGLIB 动态代理
            }else{
                // 实现了接口 =>动态代理
                Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                        interfaces,
                        // pd 代理对象, method  代理方法， args 参数集
                        (pb, method, args) -> {
                            // 前置拦截
                            // 1,遍历切面集合, 过滤出当前切到当前方法的切面
                            List<AspectInfo> beforeAspects = aspects.stream()
                                    .filter(aspectInfo -> {
                                        Before before = aspectInfo.adviceMethod.getAnnotation(Before.class);
                                        if(before == null){
                                            return false;
                                        }
                                        //
                                        String pointcutRegex = replaceTOClassPath(aspectInfo.pointcut);
                                        //判断当前方法是否匹配
                                        return method.toString().matches(pointcutRegex);
                                    }).collect(Collectors.toList());
                            // 2,调用增强方法
                            if (beforeAspects.isEmpty() == false) {
                                for (AspectInfo aspectInfo : beforeAspects) {
                                    //执行增强方法
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, new JoinPoint() {
                                        @Override
                                        public String toShortString() {
                                            return "";
                                        }

                                        @Override
                                        public String toLongString() {
                                            return "";
                                        }

                                        @Override
                                        public Object getThis() {
                                            return null;
                                        }

                                        @Override
                                        public Object getTarget() {
                                            return null;
                                        }

                                        @Override
                                        public Object[] getArgs() {
                                            return args;
                                        }

                                        @Override
                                        public Signature getSignature() {
                                            return null;
                                        }

                                        @Override
                                        public SourceLocation getSourceLocation() {
                                            return null;
                                        }

                                        @Override
                                        public String getKind() {
                                            return "";
                                        }

                                        @Override
                                        public StaticPart getStaticPart() {
                                            return null;
                                        }
                                    });
                                }
                            }
                            //执行业务方法
                            try{
                                Object ret = method.invoke(bean, args);
                                //返回拦截
                                List<AspectInfo> AfterReturning = aspects.stream()
                                        .filter(aspectInfo -> {
                                            AfterReturning afterReturning = aspectInfo.adviceMethod.getAnnotation(AfterReturning.class);
                                            if(afterReturning == null){
                                                return false;
                                            }
                                            //
                                            String pointcutRegex = replaceTOClassPath(aspectInfo.pointcut);
                                            //判断当前方法是否匹配
                                            return method.toString().matches(pointcutRegex);
                                        }).collect(Collectors.toList());
                                // 2,调用增强方法
                                if (AfterReturning.isEmpty() == false) {
                                    for (AspectInfo aspectInfo : AfterReturning) {
                                        //执行增强方法
                                        aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, new JoinPoint() {
                                            @Override
                                            public String toShortString() {
                                                return "";
                                            }

                                            @Override
                                            public String toLongString() {
                                                return "";
                                            }

                                            @Override
                                            public Object getThis() {
                                                return null;
                                            }

                                            @Override
                                            public Object getTarget() {
                                                return null;
                                            }

                                            @Override
                                            public Object[] getArgs() {
                                                return args;
                                            }

                                            @Override
                                            public Signature getSignature() {
                                                return null;
                                            }

                                            @Override
                                            public SourceLocation getSourceLocation() {
                                                return null;
                                            }

                                            @Override
                                            public String getKind() {
                                                return "";
                                            }

                                            @Override
                                            public StaticPart getStaticPart() {
                                                return null;
                                            }
                                        }, ret);
                                    }
                                }
                                return ret;
                            }catch (Throwable e){
                                //异常拦截
                                throw e;
                            }finally {
                                // 后置拦截
                                // 1,遍历切面集合, 过滤出当前切到当前方法的切面
                                List<AspectInfo> afterAspects = aspects.stream()
                                        .filter(aspectInfo -> {
                                            After after = aspectInfo.adviceMethod.getAnnotation(After.class);
                                            if(after == null){
                                                return false;
                                            }
                                            //
                                            String pointcutRegex = replaceTOClassPath(aspectInfo.pointcut);
                                            //判断当前方法是否匹配
                                            return method.toString().matches(pointcutRegex);
                                        }).collect(Collectors.toList());
                                // 2,调用增强方法
                                if (afterAspects.isEmpty() == false) {
                                    for (AspectInfo aspectInfo : afterAspects) {
                                        //执行增强方法
                                        aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, new JoinPoint() {
                                            @Override
                                            public String toShortString() {
                                                return "";
                                            }

                                            @Override
                                            public String toLongString() {
                                                return "";
                                            }

                                            @Override
                                            public Object getThis() {
                                                return null;
                                            }

                                            @Override
                                            public Object getTarget() {
                                                return null;
                                            }

                                            @Override
                                            public Object[] getArgs() {
                                                return args;
                                            }

                                            @Override
                                            public Signature getSignature() {
                                                return null;
                                            }

                                            @Override
                                            public SourceLocation getSourceLocation() {
                                                return null;
                                            }

                                            @Override
                                            public String getKind() {
                                                return "";
                                            }

                                            @Override
                                            public StaticPart getStaticPart() {
                                                return null;
                                            }
                                        });
                                    }
                                }
                            }
                        });
                // 5. 替换原有业务对象
                singtonObjects.put(id, proxyBean);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        AopApplicationContext axt = new AopApplicationContext(AopConfig.class);
        IOrderMapper bean = (IOrderMapper) axt.getBean("orderMapper");
        System.out.println("bean = " + bean);
        bean.commit("12");
    }
    // 把切点的表达式转换为可以和其他类匹配的正则表达式
    public static String replaceTOClassPath(String pointCut){
        String pointcut = pointCut.replaceFirst("execution\\((.+)\\)", "$1");
        // 将 .. 和 * 替换为 #     # 作为一个临时的替换字符
        String pointcutRegex = pointcut.replaceAll("\\.{2}|\\*", "#");
        // 将 . 替换成 \. 用于匹配包和类的分割符
        pointcutRegex = pointcutRegex.replaceAll("\\.", "\\\\.");
        // 将 # 号 替换为 .*
        pointcutRegex = pointcutRegex.replaceAll("#", ".*");
        // 替换 小括号(
        pointcutRegex = pointcutRegex.replaceAll("\\(", "\\\\(");
        // 替换 小括号)
        pointcutRegex = pointcutRegex.replaceAll("\\)", "\\\\)");
        return pointcutRegex;
    }
}
@Data
class AspectInfo{
    String pointcut; // 切点
    Method adviceMethod; // 增强方法
    Object aspectBean; // 切面对象

    //替换切点表达式
    void replacePointcut() throws NoSuchMethodException {
        if(pointcut.matches("(\\w+)\\(\\)")){
            String pointcutMethod = pointcut.replaceFirst("(\\w+)\\(\\)", "$1");
            Method method = aspectBean.getClass().getDeclaredMethod(pointcutMethod);
            Pointcut pointcutAnno = method.getAnnotation(Pointcut.class);
            pointcut = pointcutAnno.value();
        }
    }

}
